using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using Microsoft.Samples.ServiceHosting.StorageClient;
using Microsoft.Samples.ServiceHosting.PersonalWebSite;
using System.Net;
using System.Collections.Specialized;
using System.IO;
using System.Data.Services.Client;

namespace BackupObjects
{
    public enum VisibilityBackup
    {
        AccesibleByName,
        AccesibleByGuid,
        Private
    }
    class BackupObjectRow : TableStorageEntity
    {

        #region functionality
        // id is partitionKey
        // rowKey is always an empty string
        public BackupObjectRow(string partitionKey)
        {
            SecUtility.CheckParameter(ref partitionKey, true, true, true, TableStorageConstants.MaxStringPropertySizeInChars, "partitionKey");
            PartitionKey = partitionKey;
            RowKey = string.Empty;
            TagIds = new List<Guid>();

        }

        public BackupObjectRow()
            : base()
        {
        }

        public override string PartitionKey
        {
            set
            {
                ID = value;
            }
            get
            {
                return ID;
            }
        }

        public override string RowKey
        {
            get
            {
                return base.RowKey;
            }
            set
            {
                base.RowKey = value;
            }
        }
        #endregion
        public string ID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string UploadBy { get; set; }
        public DateTime UploadDate { get; set; }
        public string Visibility { get; set; }
        //TODO: 
        public string OriginalFileName { get; set; }

        /// <summary>
        /// Gets or sets the tag ids for the given object.
        /// </summary>
        public List<Guid> TagIds { get; set; }

    }
    public class BackupObject : ITaggable
    {
        public BackupObject()
        {
            ID = Guid.NewGuid();
            Visibility = VisibilityBackup.Private;
            TagIds = new List<Guid>();
        }
        internal BackupObject(BackupObjectRow row)
            : this()
        {
            //TODO : use AutoMApper
            this.ID = Guid.Parse(row.ID);
            this.Name = row.Name;
            this.Visibility = (VisibilityBackup)(int)Enum.Parse(typeof(VisibilityBackup), row.Visibility);
            this.OriginalFileName = row.OriginalFileName;
            this.Description = row.Description;
            this.UploadDate = row.UploadDate;
            this.UploadBy = row.UploadBy;

            LoadTagIds();
        }

        public Guid ID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public byte[] Contents { get; set; }
        public string UploadBy { get; set; }
        public DateTime UploadDate { get; set; }
        public VisibilityBackup Visibility { get; set; }
        public string OriginalFileName { get; set; }

        /// <summary>
        /// Gets or sets the tag ids for the given object.
        /// </summary>
        public List<Guid> TagIds { get; set; }

        /// <summary>
        /// Load the tag ids list.
        /// </summary>
        public void LoadTagIds()
        {
            //TODO: Use IoC
            BackupTagRepository backupObjectTagRepository = new BackupTagRepository();
            TagIds = backupObjectTagRepository.GetTagsId(ID);
        }

        //TODO: Name conventation for property: PascalCase - 1st letter Upper. 
        public string icofilename
        {
            get { return Path.GetExtension(OriginalFileName).Replace(".", "") + ".png"; }
        }
    }
    public class BackupObjectsList : List<BackupObject>
    {
        static BackupObjectsList()
        {
            if (myAssembly == null)
            {
                lock (lokcobj)
                {
                    if (myAssembly == null)
                    {
                        myAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                        ResourceIco = myAssembly.GetManifestResourceNames();
                    }
                }
            }
        }

        private static BackupTagRepository backupObjectTagRepository;
        private static System.Reflection.Assembly myAssembly;
        private static string[] ResourceIco;
        private static readonly object lokcobj = new object();
        private static TableStorage _tableStorage;
        private static BlobStorage _blobStorage;
        private static string _tableName = "backupsTable";
        private static StorageAccountInfo _account;
        private static string _accountName;
        private static string _accountSharedKey;
        private static string _storageEndpoint;
        private static bool _usePathStyleUris;
        private const int NumRetries = 3;
        private static RetryPolicy _tableRetry = ProviderRetryPolicies.RetryN(NumRetries, TimeSpan.FromSeconds(1));

        public static void Initialize()
        {

            try
            {

                StorageAccountInfo blobAccount = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration();
                _accountName = blobAccount.AccountName;
                _accountSharedKey = blobAccount.Base64Key;
                _storageEndpoint = blobAccount.BaseUri.AbsolutePath;
                _usePathStyleUris = blobAccount.UsePathStyleUris;


                _account = StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration();

                _blobStorage = BlobStorage.Create(blobAccount);
                _blobStorage.RetryPolicy = RetryPolicies.RetryN(NumRetries, TimeSpan.FromMilliseconds(100));

                _tableStorage = TableStorage.Create(_account);
                _tableStorage.TryCreateTable(_tableName);

                backupObjectTagRepository = new BackupTagRepository();
            }
            catch (Exception e)
            {
                Log.Write(EventKind.Error, "Initialization of data service structures for backup storage failed!");
                throw new Exception("Could not create or find backup table / blob table  in the data service ", e);
            }
        }


        private static TableStorageDataServiceContext CreateDataServiceContext()
        {
            return _tableStorage.GetDataServiceContext();
        }
        internal BackupObject Load(Guid id)
        {
            ReInitialize();

            string sID = id.ToString();
            TableStorageDataServiceContext svc = CreateDataServiceContext();
            var query = svc.CreateQuery<BackupObjectRow>(_tableName).Where(item => item.PartitionKey == sID);
            TableStorageDataServiceQuery<BackupObjectRow> q = new TableStorageDataServiceQuery<BackupObjectRow>(query as DataServiceQuery<BackupObjectRow>, _tableRetry);
            IEnumerable<BackupObjectRow> backups = q.ExecuteWithRetries();
            int count = 0;
            BackupObject ret = null;
            foreach (BackupObjectRow row in backups)
            {
                if (count++ > 1)
                {
                    Log.Write(EventKind.Warning, "There are multiple backups with the same ID (ID={0}) in the backup table. backup ID should be unique.", id);
                }
                ret = new BackupObject(row);

            }
            return ret;


        }

        /// <summary>
        /// Get all the backup entities that have the given tags.
        /// </summary>
        /// <param name="tagids">The tag list.</param>
        /// <returns>The list of entities with the tag ids.</returns>
        internal List<BackupObject> GetBackupEntitiesByTags(List<Guid> tagIds)
        {
            //1. Get the list of obj. that have the first given tag.
            //2. Refine the search and remove obj. that don't have the rest of tag ids.

            if (tagIds.Count == 0)
            {
                return new List<BackupObject>();
            }

            var backupObjects = GetBackupEntitiesByTag(tagIds.First());

            if (tagIds.Count == 1)
            {
                return backupObjects;
            }

            for (int i = 1; i < tagIds.Count; i++)
            {
                backupObjects.RemoveAll(item => !item.TagIds.Contains(tagIds[i]));
            }

            return backupObjects;
        }

        /// <summary>
        /// Get all backup entities that with a given tag id.
        /// </summary>
        /// <param name="tagId">The tag id.</param>
        /// <returns>The collection of backup entities with the given tag id.</returns>
        internal List<BackupObject> GetBackupEntitiesByTag(Guid tagId)
        {
            if (tagId == default(Guid))
            {
                return new List<BackupObject>();
            }
            ReInitialize();

            var backupObjIds= backupObjectTagRepository.GetBackupObjectsId(tagId);
            List<BackupObject> backupObjects=new List<BackupObject>();
            foreach (var id in backupObjIds)
            {
                var backupObject = Load(id);
                if(backupObject!=null)
                {
                    backupObjects.Add(backupObject);
                }
            }

            return backupObjects;
        }


        internal BackupObject Load(string Name)
        {
            return null;
        }
        private static void RemoveContentFromBlobStorage(BackupObjectRow row)
        {
            BlobContainer container = _blobStorage.GetBlobContainer(row.ID);
            try
            {
                container.DeleteBlob(row.ID.ToString());
            }
            catch (StorageException se)
            {
                Log.Write(EventKind.Warning, "The deletion of content file for the backup (ID={0}) from the blob storage failed.", row.ID);
                throw new Exception("Failed to access blob storage ", se);
            }
        }
        private static void AddContentToBlobStorage(BackupObjectRow newBor, byte[] Content, byte[] icoContents)
        {
            BlobContainer container = _blobStorage.GetBlobContainer(newBor.ID.ToString());
            NameValueCollection containerMetadata = new NameValueCollection();
            containerMetadata.Add("Name", "backupcontainer");
            container.CreateContainer(containerMetadata, ContainerAccessControl.Private);
            string blobName = newBor.ID.ToString();
            MemoryStream blobContent = new MemoryStream(Content);
            BlobProperties properties = new BlobProperties(blobName);
            container.CreateBlob(properties, new BlobContents(blobContent), true);
            BlobProperties icoprop = new BlobProperties(blobName + "ico");
            container.CreateBlob(icoprop, new BlobContents(icoContents), true);


        }
        private byte[] GetImageFromExtension(string ext)
        {
            //TODO :  find image in list of ico's

            if (ext == null) ext = "";
            ext = ext.Replace(".", "");
            Bitmap objBitmap = null;
            try
            {
                var name = ResourceIco.FirstOrDefault(item => item.Contains("." + ext + "."));
                if (name != null)
                {

                    Stream myStream = myAssembly.GetManifestResourceStream(name);
                    if (myStream != null)
                    {
                        objBitmap = new Bitmap(myStream);
                    }
                }
            }
            catch (FileNotFoundException)
            {

            }
            if (objBitmap == null)
            {
                objBitmap = new Bitmap(25, 25, PixelFormat.Format24bppRgb);
                var objGraphic = Graphics.FromImage(objBitmap);
                var whiteBrush = new SolidBrush(Color.White);
                var redBrush = new SolidBrush(Color.Red);
                objGraphic.FillRectangle(whiteBrush, 0, 0, 25, 25);
                Font font = new Font("Arial", 10);
                objGraphic.DrawString(ext, font, redBrush, 0, 0);
            }
            using (MemoryStream ms = new MemoryStream())
            {
                objBitmap.Save(ms, ImageFormat.Jpeg);
                return ms.ToArray();
            }
        }

        internal void Save(BackupObject bo)
        {
            ReInitialize();

            //var oldObj = Load(bo.ID);
            //if(oldObj!=null)
            //{
            //    Delete(bo.ID);
            //    bo.Contents = oldObj.Contents;
            //}

            //TODO : AutoMapper?
            BackupObjectRow newBor = new BackupObjectRow(bo.ID.ToString());
            newBor.ID = bo.ID.ToString();
            newBor.Name = bo.Name;
            //AddContentToBlobStorage(newBor, bo.Contents);
            newBor.Description = bo.Description;
            newBor.Visibility = bo.Visibility.ToString();
            newBor.UploadBy = bo.UploadBy;
            newBor.UploadDate = bo.UploadDate;
            newBor.OriginalFileName = bo.OriginalFileName;
            try
            {               
                AddContentToBlobStorage(newBor, bo.Contents, GetImageFromExtension(Path.GetExtension(bo.OriginalFileName)));
                TableStorageDataServiceContext svc = CreateDataServiceContext();
                svc.AddObject(_tableName, newBor);
                svc.SaveChanges();
            }
            catch (InvalidOperationException e)
            {
                HttpStatusCode status;
                // when retry policies are used we cannot distinguish between a conflict and success
                // so, in the case of a conflict, we just retrun success here
                if (TableStorageHelpers.EvaluateException(e, out status) && status == HttpStatusCode.Conflict)
                {
                    return;
                }

                Log.Write(EventKind.Warning, "Could not add the backup (ID={0}) to the backup table.", bo.ID);
                throw new Exception("Failed to access the backup table ", e);
            }
        }
        internal void LoadAll()
        {
            ReInitialize();

            TableStorageDataServiceContext svc = CreateDataServiceContext();
            IEnumerable<BackupObjectRow> query = svc.CreateQuery<BackupObjectRow>(_tableName);
            TableStorageDataServiceQuery<BackupObjectRow> q = new TableStorageDataServiceQuery<BackupObjectRow>(query as DataServiceQuery<BackupObjectRow>, _tableRetry);
            IEnumerable<BackupObjectRow> rows = q.ExecuteAllWithRetries();
            if (rows != null && rows.Count() > 0)
            {
                foreach (var row in rows)
                {
                    var bo = new BackupObject(row);


                    this.Add(bo);
                }

            }

        }

        internal void Delete(Guid id)
        {
            ReInitialize();

            string sID = id.ToString();
            TableStorageDataServiceContext svc = CreateDataServiceContext();
            var query = svc.CreateQuery<BackupObjectRow>(_tableName).Where(item => item.PartitionKey == sID);
            TableStorageDataServiceQuery<BackupObjectRow> q = new TableStorageDataServiceQuery<BackupObjectRow>(query as DataServiceQuery<BackupObjectRow>, _tableRetry);
            IEnumerable<BackupObjectRow> backups = q.ExecuteWithRetries();
            int count = 0;
            BackupObjectRow ret = null;
            foreach (BackupObjectRow row in backups)
            {
                if (count++ > 1)
                {
                    Log.Write(EventKind.Warning, "There are multiple backups with the same ID (ID={0}) in the backup table. backup ID should be unique.", id);
                    return;
                }
                ret = row;

            }
            if (count != 1)
            {
                Log.Write(EventKind.Warning, "not found backup id {0} in the backup table. .", id);
                return;
            }

            RemoveContentFromBlobStorage(ret);
            svc.DeleteObject(ret);
            svc.SaveChanges();

        }
        internal Stream GetBackupStream(Guid id)
        {
            return GetBackupStream(id.ToString());
        }
        internal Stream GetBackupStream(string id)
        {
            return GetBackupStream(id, id);
        }
        internal Stream GetBackupStream(string idcontainer, string idblob)
        {
            ReInitialize();

            BlobContainer container = _blobStorage.GetBlobContainer(idcontainer);
            try
            {
                MemoryStream outputStream = new MemoryStream();
                container.GetBlob(idblob.ToString(), new BlobContents(outputStream), false);
                outputStream.Seek(0, SeekOrigin.Begin);
                return outputStream;
            }
            catch (StorageClientException se)
            {
                Log.Write(EventKind.Warning, "Could not retrieve the backup stream files for the backup (ID={0} ,blog={1}) from the blob storage.", idcontainer, idblob);
                throw;
            }
        }

        /// <summary>
        /// Reinitialize the storage if is null.
        /// </summary>
        //TODO: Refactorize this - use Singleton on process
        private static void ReInitialize()
        {
            if (_blobStorage == null || _tableStorage == null)
            {
                Initialize();
            }
        }
    }
}
